package com.hqyj.springbootdemo01.common.config;

import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author sdx2009
 * @package com.hqyj.springbootdemo01.common.config
 * @date 2022/8/30 10:19
 */

/**
 * 执行顺序：prehandle->isAccessAllowed->isLoginAttempt->executeLogin->onAccessDenied
 */
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * shiro跨域问题处理
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
//        允许跨域配置
        res.setHeader("Access-control-Allow-Origin", req.getHeader("Origin"));
        res.setHeader("Access-Control-Allow-Methods", "PUT,GET,POST,DELETE");
        res.setHeader("Access-Control-Allow-Headers", req
                .getHeader("Access-Control-Request-Headers"));

        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            res.setStatus(HttpStatus.OK.value());
            return true;
        }
        return super.preHandle(request, response);
    }

    /**
     * 业务逻辑：（根据实际业务情况自定义）
     * 1.判断请求中是否携带token（isloginAttempt）
     * 2.是则执行登录方法（executeLogin）
     * 3.如果没有携带token，直接返回true，原因：
     * 为了让匿名用户也可以浏览网页，当接口需要身份认证时，在接口添加相应的注解
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response,
                                      Object mappedValue) {

//        判断请求中是否有token
        if (isLoginAttempt(request, response)) {
            try {
//                获取subject和token进行身份认证
                executeLogin(request, response);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
//                如果为false，则跳转到onAccessDenied方法
                return false;
            }
        }
//        返回true是为让匿名用户可以浏览网页
        return false;
    }

    /**
     * 当请求身份认证失败时进行错误处理
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//        TODO 自定义异常信息处理
        response401(request, response);
        return false;
    }

    /**
     * 判断请求中是否携带token
     * 如果不为空返回true
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {

        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader("Authorization");
        return StringUtils.isNotEmpty(authorization);
    }

    /**
     * 获取当前subject和请求中的token，进行身份认证
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {

        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader("Authorization");
//        这里获取到的是JWT生成的token
        JWTToken token = new JWTToken(authorization);


//        不建议
//        DecodedJWT decodedJWT = JWT.decode(authorization);
//        String userName = decodedJWT.getClaim("userName").asString();
//        UsernamePasswordToken token = new UsernamePasswordToken(userName, "");

        getSubject(request, response).login(token);

        return true;
    }

    private void response401(ServletRequest request, ServletResponse response) {
        try {
            HttpServletResponse res = (HttpServletResponse) response;
            res.sendRedirect("/api/user/401");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
